
# The library system loads all the game data from a compressed tarfile
# Then, each game item is made available using the Resource() factory function

from xml.etree import ElementTree as xml
import tarfile, os, atexit, mimetypes, pygame, marshal

# Open a xml file, extract all nodes with tag t and return a
# dictionary of extracted nodes, indexed by their k attribute
def xml_map(tree, tag, key="id"): 
    return dict((i.attrib[key], i) for i in tree.findall(tag))


# Similar to prev method, but this returns an iterator instead of a list
def xml_iter(tree, tag, key="id"):
    for i in tree.findall(tag): yield (i.attrib[key], i)


# Fetch and initialize Game Resources
class Resource(object):
    
    # This method must be called before doing anything else with the Resource
    @classmethod
    def library_init(cls, game, save_state = None, dir_path = None):
        
        # The name of our game :)
        cls.name = game
        cls._fetched = dict()

        if not dir_path:
            # Create a temporary path to uncompress the game data
            cls.tempdir = os.tempnam()
            os.mkdir(cls.tempdir)
            
            # Open the compressed archive and load the game state
            fname = game + ".tbz2"
            if os.path.exists(fname) and tarfile.is_tarfile(fname):
                tarfile.open(fname).extractall(cls.tempdir)
            else:
                raise ValueError, fname + " is not a valid game file"
            
            # Setup a cleanup function to remove unused files on exit
            def cleanup(cls=cls):
                path = os.path.join(cls.tempdir, cls.name)
                for each in os.listdir(path): os.unlink(os.path.join(path, each))
                os.rmdir(path)
                os.rmdir(cls.tempdir)
                    
            atexit.register(cleanup)
            
        else:
            cls.tempdir = dir_path
            
        
    # Extract a file from the tarfile archive and return a path to it
    # If the path was already extracted, just return it
    def __new__(cls, *paths):        
        
        # If item has been previously loaded, just return it
        path = Resource.path(*paths)
        if path in cls._fetched: return cls._fetched[path]
            
        # Determine the MIME encoding of the requested item
        mtype, encoding = mimetypes.guess_type(path)
        if mtype is not None:
            pretype, postype = mtype.split('/')
        else:
            pretype, postype = "unknown", "unknown"
            
        # Use the MIME data to determine how to load item to memory
        if pretype == "image": 
            item = pygame.image.load(path).convert_alpha()
        elif "xml" in postype: 
            item = xml.parse(path)
        else: item = open(path)
            
        # Store item on internal dict and return requested item
        cls._fetched[path] = item
        return item
            
    # Compose the path to a given Resource
    # Automatically finds a file even if filetype is ommited
    @classmethod
    def path(cls, *paths):
        name = os.path.join(*paths)
        if not name:
            raise TypeError, "Empty path requested."
        if '.' not in paths[0]:
            head = os.path.join(cls.tempdir, cls.name)
            test = paths[0] + '.'
            for name in os.listdir(head):
                if name.startswith(test): break
            else:
                ValueError("Resource item not found in archive.")
        return os.path.join(cls.tempdir, cls.name, name)
    
    
# Defines a descriptor property that can remember information
# from one initialization to the next
class State(dict):    
    
    # Initialize a state property that keeps information between instances
    def __init__(self, storage, key):
        
        # Make sure that state received the proper arguments
        assert isinstance(storage, dict), TypeError('Storage must be a mapping object')
        assert isinstance(key, basestring), TypeError('Key must be a string id')

        # Initialize state
        dict.__init__(self)
        self.__signature = (storage, key)
        
        # Load previously existing state data if possible
        if key in storage: 
            for k, v in storage[key].items(): self[k] = marshal.loads(v)

        
    # When the container object for state dies, the information will be saved
    # Objects that are un-marshalable will not be stored (this is a design decision)
    def __del__(self):
        storage, key = self.__signature; items = dict()
        for k, v in self.items():
            try: items[k] = marshal.dumps(v)
            except ValueError:
                continue
        storage[key] = items

    
    
    
    
    
    
    
    
    
    
    
    
        
